/** 
 * This file is part of osami-fin-demo project( http://code.google.com/p/osami-fin-demo/ )
 * @license Apache License 2.0
 */
package org.osami.appevent;

import java.util.Date;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.osgi.framework.BundleContext;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.PhoneStateListener;
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.util.Log;

/** Base class for listening for phone events */
abstract class BaseReceiver extends BroadcastReceiver {

	EventDispatcher eventDispatcher;
	Logger log;

	public BaseReceiver(EventDispatcher aEventDispatcher) {
		super();
		this.eventDispatcher = aEventDispatcher;
		this.log = new Logger(this.getClass().getName());
	}
}

/**
 * Listens for phone states while the call is on and creates OSGi events of
 * them.
 */
class CallStateListener extends PhoneStateListener {

	Logger log;

	protected Context context;
	protected boolean incoming;
	protected String phoneNumber;
	protected String callerName;
	protected EventDispatcher eventDispatcher;

	public void updateCallData(Context aContext, boolean incoming,
			String phoneNumber) {
		this.context = aContext;
		this.phoneNumber = phoneNumber;
		this.incoming = incoming;

		callerName = LocalUtilities.getDisplayNameForPhoneNumber(context,
				this.phoneNumber);
	}

	public CallStateListener(EventDispatcher aEventDispatcher) {
		super();

		this.log = new Logger(this.getClass().getName());
		this.eventDispatcher = aEventDispatcher;
	}

	// TODO: HashMap?
	public static String phoneStateToEventState(int aCallState) {
		switch (aCallState) {

		case TelephonyManager.CALL_STATE_IDLE:
			return EventDispatcher.CALL_STATE_IDLE;

		case TelephonyManager.CALL_STATE_OFFHOOK:
			return EventDispatcher.CALL_STATE_OFFHOOK;

		case TelephonyManager.CALL_STATE_RINGING:
			return EventDispatcher.CALL_STATE_RINGING;
		default:
			Log.w("CallStateListener", String.format(
					"Unknown call state :%d, 0x%x", aCallState, aCallState));
		}
		return "";
	}

	public void makeEvent(String aEventType) {
		if (incoming) {
			this.eventDispatcher.makeEventCallIncoming(aEventType,
					this.phoneNumber, callerName);
		} else {
			this.eventDispatcher.makeEventCallOutgoing(aEventType,
					this.phoneNumber, callerName);
		}
	}

	public void makeEvent(int aCallState) {
		String eventType = CallStateListener.phoneStateToEventState(aCallState);
		makeEvent(eventType);
	}

	public void onCallStateChanged(int aCallState, String aIncomingNumber) {
		log.d("Got aCallState", aCallState);

		if (context == null) {
			log.e("Context not set");
			return;
		}

		// Let the receiver handle this one
		if (aCallState == TelephonyManager.CALL_STATE_IDLE) {
			return;
		}

		this.makeEvent(aCallState);
	}
}

/** Listens for phone state changes and outgoing call event */
class PhoneStateReceiver extends BaseReceiver {
	boolean isOutGoing = false;
	CallStateListener phoneListener = null;

	public PhoneStateReceiver(EventDispatcher aEventDispatcher) {
		super(aEventDispatcher);
	}

	@Override
	public void onReceive(Context aContext, Intent aIntent) {
		log.d("onReceive");

		TelephonyManager phone = LocalUtilities.getTelephony(aContext);
		if (phone == null) {
			return;
		}

		log.df("phoneState:%d", phone.getCallState());

		// The listener does not work properly with outgoing call
		// We get IDLE while waiting for answer in PhoneStateListener.
		// Works OK here so we need to check the idle state here.
		// The first IDLE comes when the call is starting(incoming\outgoing).
		// We need to start the phone listener on first IDLE and stop it on the
		// second.
		if (phone.getCallState() == TelephonyManager.CALL_STATE_IDLE) {
			if (phoneListener != null) {

				// Assuming this happens only after a call
				phoneListener.makeEvent(EventDispatcher.CALL_STATE_IDLE);

				phone.listen(phoneListener, PhoneStateListener.LISTEN_NONE);
				phoneListener = null;
				log.d("Call listener disabled");
				return;
			}
		}

		if (phoneListener == null) {
			phoneListener = new CallStateListener(this.eventDispatcher);
			log.d("Created new CallStateListener");
		}

		if (aIntent.hasExtra(Intent.EXTRA_PHONE_NUMBER)) {
			log.d("OUTGOING call: Has extra phone number");

			String number = aIntent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
			phoneListener.updateCallData(aContext, false, number);
			phone.listen(phoneListener, PhoneStateListener.LISTEN_CALL_STATE);

		} else {
			String number = aIntent
					.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
			if (number != null) {
				log.d("INCOMING call", number);
				phoneListener.updateCallData(aContext, true, number);
				phone.listen(phoneListener,
						PhoneStateListener.LISTEN_CALL_STATE);
			} else {
				log.d("No number");
			}
		}
	}
}

/** Listens for SMS events and creates OSGi event about it */
class IncomingSMSReceiver extends BaseReceiver {
	public IncomingSMSReceiver(EventDispatcher aEventDispatcher) {
		super(aEventDispatcher);
	}

	@Override
	public void onReceive(Context context, Intent intent) {
		
		Bundle bundle = intent.getExtras();

		Object messages[] = (Object[]) bundle.get("pdus");
		SmsMessage smsMessage[] = new SmsMessage[messages.length];
		for (int n = 0; n < messages.length; n++) {
			smsMessage[n] = SmsMessage.createFromPdu((byte[]) messages[n]);
		}
		
		String smsSenderNumber = new String(smsMessage[0].getOriginatingAddress());
		smsSenderNumber = smsSenderNumber.replaceFirst("\\+\\d\\d\\d", "0");
		String smsSenderName = LocalUtilities.getDisplayNameForPhoneNumber(context, smsSenderNumber);
		
		this.eventDispatcher.makeEventSMS(smsMessage[0].getMessageBody(), smsSenderNumber, smsSenderName);
	}
}


/** Listens for events from application and creates OSGi event about it */
class ApplicationEventReceiver extends BaseReceiver {
	
	Logger log = new Logger(this.getClass().getName());
	
	public ApplicationEventReceiver(EventDispatcher aEventDispatcher) {
		super(aEventDispatcher);
	}

	@Override
	public void onReceive(Context context, Intent intent) {
		
		JSONObject json;
		
		log.d("onReceive");
		
		Bundle bundle = intent.getExtras();
		String topic = bundle.getString("topic");
		String jsonString = bundle.getString("json");
		
		try {
			json = new JSONObject(jsonString);
			this.eventDispatcher.makeEventApplication(topic, json);
		} catch (JSONException e) {
			e.printStackTrace();
			log.e(e.getMessage());
		}		
	}
}

/** An Intent bridge for Hid Manager for querying available devices */
class HidManagerBridgeReceiver extends BaseReceiver
{
	public final static String INTENT_ACTION_QUERY = "org.osami.HID_MANAGER_QUERY";
	public final static String INTENT_ACTION_RESPONSE = "org.osami.HID_MANAGER_RESPONSE";
	
	Logger log = new Logger(this.getClass().getName());
	
	protected Context androidContext;
	public HidManagerBridgeReceiver(EventDispatcher aEventDispatcher, Context androidContext)
	{
		super(aEventDispatcher);
		this.androidContext = androidContext;
	}
	
	public boolean handleQuery(Context context, Intent aIntent) throws JSONException   
	{
		
		Bundle extras = aIntent.getExtras();
		if(extras == null){
			log.d("No extras");
			return false;
		}
		
		if(extras.containsKey("query"))
		{
			Intent i = new Intent();		
			
			i.setAction(INTENT_ACTION_RESPONSE);
			
			log.d("Received query");
			
			// ID of HID device to ask
			String deviceID = extras.getString("deviceID");
			String params   = extras.getString("query");
			 
			log.df("Got params:%s", params);
			
			// TODO: Real query for getting programs. The device should give the json
			// org/osami/PROGRAMS etc
			if(params.equals("org/osami/CHANNELS")){
				// Return list of channels
				JSONArray channels;
				JSONObject channel;
				
				channels = new JSONArray();
				channel = new JSONObject();
			 
				channel.put("id", "tv1");
			
				channel.put("name", "TV1");
				channel.put("showing", "Uutiset");
				channels.put(channel);
				
				i.putExtra("channels", channels.toString());
				
				log.d("Sending list of channels");
				context.sendBroadcast(i);				 
				
				return true;
				
			}
			else if(params.equals("org/osami/PROGRAMS"))
			{ 
				// Return list of programs running on channel
				String channel = extras.getString("channel");
				
				JSONArray programs = new JSONArray();
				JSONObject prog = new JSONObject();
				
				prog.put("name", "Uutiset");
				prog.put("description", "Valtakunnan kuumimmat ja polttavimmat jutut");
				prog.put("start", new Date().getTime());
				prog.put("end", new Date().getTime() + 60 * 60 );
				
				programs.put(prog);
				i.putExtra("programs", programs.toString());
				
				log.d("Sending list of programs");
				context.sendBroadcast(i);
				
				return true;
			}
			  
		}
		
		return false;
	}
	@Override
	public void onReceive(Context context, Intent aIntent) {
		
		log.d("onReceive");
		
		// TODO: This is dummy data, query real devices from HidManager
		//Bundle extras = aIntent.getExtras();
		try {
			//if(extras.containsKey("json")){
			log.d("handleQuery?");
			
			boolean handled = handleQuery(context, aIntent);
			if(handled){
				log.d("handleQuery true");
				return;
			}
			//}
			
			log.d("Received device query");
			
			// Construct a JSON holding the devices and pass it on
			JSONArray devices_json = new JSONArray();
			 
			JSONObject dev = new JSONObject();
			
			String baseurl = "http://192.168.0.115:8080/";
			
			dev.put("name", "Telly");
			dev.put("id", "telly-controller");
			dev.put("description", "TV controller");
			dev.put("icon_url", baseurl + "telly.png");
			dev.put("ui_url", baseurl + "index.html");
			
			devices_json.put(dev);
			
			dev = new JSONObject();
			
			dev.put("name", "Sauna");
			dev.put("id", "sauna-controller");
			dev.put("description", "Sauna statusboard");
			dev.put("icon_url", baseurl + "sauna.png");
			dev.put("ui_url", baseurl + "sauna.html");
			
			devices_json.put(dev);
		 
			Intent i = new Intent();		
			
			i.setAction(INTENT_ACTION_RESPONSE);
			i.putExtra("devices", devices_json.toString());
			
			context.sendBroadcast(i);
		}
			
		catch (Exception e) 
		{
			StackTraceElement[] traces = e.getStackTrace();
			StackTraceElement trace = traces[0];
			
			log.e(e.toString());
			log.ef("class:%s line:%d", trace.getClassName(), trace.getLineNumber());
			
			return;
		}
		
	}
}

/** Listens for requests to convert specific OSGi events to Android Intent */
class OSGi2IntentReceiver extends BaseReceiver 
{
	protected Context androidContext;
	protected BundleContext bundleContext;
	
	public OSGi2IntentReceiver(EventDispatcher aEventDispatcher, 
			Context androidContext,
			BundleContext bundleContext) 
	{
		super(aEventDispatcher);
		
		// TODO: Have these in EventDispatcher instead so no need to pass them around.
		this.bundleContext = bundleContext;
		this.androidContext = androidContext;
		
	}
	
	@Override
	public void onReceive(Context context, Intent intent) {
		
		Bundle bundle = intent.getExtras();
		String topic = bundle.getString("topic");
		String intentName = bundle.getString("intent");
		
		this.eventDispatcher.requestOSGi2Intent(topic, intentName, androidContext, bundleContext);
		
	}
}
